#!/usr/bin/env python
# -*- coding: utf8 -*-
#
#
# $Id: picasapush.py 13 2007-07-19 01:12:02Z havard.gulldahl $
#
# Copyright 2007-2008 Håvard Gulldahl
#
# Licensed under the General Public License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
This utility allows PicasaFS filesystems to be mounted with the regular
'mount' command, or even to be listed in /etc/fstab

Usage:
     1.  mount.picasafs none /path/of/mount/point [options]
     2.  in /etc/fstab, add:
           none /path/of/mount/point picasafs noauto[,options]
"""

import sys, os, os.path, logging, time, optparse, getpass, re, tempfile, signal

DEFAULT_PICASAFS_LOCATION     = '/usr/share/picasafs/picasafs.py'
DEFAULT_PICASAFS_LOCATION     = './picasafs.py'
# delay (in seconds) after which we consider picasafs could not mount, and
# we abort.
picasafs_MOUNTING_MAX_DELAY   = 20
# delay (in seconds) between two consecutive checks for encfs still being
# mounted.
ENCFS_MOUNTPOINT_CHECK_DELAY = 5  # in seconds

def parseCommandLineArgs(args):
  usage = "usage: %prog none mountpoint [-o <options>] [-p]"

  parser = optparse.OptionParser(usage=usage)
  parser.add_option("-p", "--prompt-for-password", dest="promptForPasswd",
                    action="store_true", default=False,
                    help="Prompt for Google Photos password" )
  parser.add_option("-e", "--use-transparent-encryption", dest="useEncfs",
                    action="store_true", default=False,
                    help="Use transparent encryption" )
  parser.add_option("-o", "--options", dest="picasafsOptions",
                    help="Use those options", metavar="option1=value1,[option2=value2,...]",
                    default="")

  options, args = parser.parse_args(args)

  if len(args) != 2:
    parser.error("Wrong number of arguments")
  else:
    pyfile, mountpoint = args
    mountpoint = os.path.abspath(mountpoint)
  odata = options.picasafsOptions

  namedOptions = {}
  if odata:
    for o in odata.split(","):
     try:
       k, v = o.split("=", 1)
       namedOptions[k] = v
     except:
       print "Ignored option :"+o

  if options.promptForPasswd:
    namedOptions['password'] = getpass.getpass("Gmail password: ")


  # pyfile sanitation
  if pyfile == 'none':
    pyfile = DEFAULT_PICASAFS_LOCATION
  else:
    pyfile = os.path.abspath(pyfile)
  if not os.path.isfile(pyfile):
    logging.error("file %s doesn't exist, or is not a file" % pyfile)
    sys.exit(1)

  return pyfile, mountpoint, namedOptions, options.useEncfs

def main(mountpoint, namedOptions, useEncfs=False):
  if useEncfs: # create a temp. dir to mount the encrypted picasafs volume
    clearMountPoint = mountpoint
    mountpoint = tempfile.mkdtemp()

  picasafsPID = os.fork()
  if picasafsPID == 0:
    # this is the child process. We register a log-and-exit handler for
    # SIGHUP, then we start the actual child task: mounting the picasafs
    childPID = os.getpid()
    logging.info('Starting picasafs in child process (PID %s)' % childPID)

    def handlerHUP(signum, frame):
      logging.warning("Child process %s received SIGHUP, exiting..." % childPID)
      os._exit(1)
    #signal.signal(signal.SIGHUP, handlerHUP)

    sys.argv = [] # blank the command-line params for this child
    server = picasafs.picasafs(namedOptions, mountpoint)
    server.multithreaded = 1
    server.debug = 1
    server.main()
    logging.info('picasafs terminated')
  else:
    # this is the parent process. First we wait for picasafs to become mounted
    # within picasafs_MOUNTING_MAX_DELAY seconds.
    # If this doesn't happen, we cleanup and exit.
    logging.info('waiting for %s to become a mountpoint' % mountpoint)
    for i in range(picasafs_MOUNTING_MAX_DELAY): # give our child some time
      if not os.path.ismount(mountpoint): # not yet mounted
        # let's check our child is still alive, without blocking
        pid, status = os.waitpid(picasafsPID, os.WNOHANG)
        if not pid == 0: # it died, let's exit
          logging.error('picasafs child died, exiting...')
          sys.exit(1)
      else: # success, picasafs got mounted
        break
      time.sleep(1)

    if os.path.ismount(mountpoint):
      logging.info('%s is now a mountpoint' % mountpoint)
    else: # picasafs wasn't mounted in the allocated time frame, abort
      logging.error("picasafs did not connect in less than %s seconds, aborting..." % picasafs_MOUNTING_MAX_DELAY)
      os.kill(picasafsPID, signal.SIGHUP) # send SIGHUP to our child
      os.waitpid(picasafsPID, 0) # upon receiving SIGHUP, it should exit, so we block-wait on it
      logging.info('Successfully reaped child %s' % picasafsPID)
      sys.exit(3)

    if useEncfs:
      # encfs was requested. The first thing we do is define a cleanup()
      # function to unmount the actual picasafs volume and remove the temporary
      # mountpoint.
      # We then spawn a child to run encfs, and wait for the encfs process
      # to either return an error (in which case we cleanup and
      # exit), or background itself (in which case we fork a child that
      # periodically checks if encfs is still mounted, and deos the cleanup
      # when it's not anymore.

      def cleanup(exitCode):
        logging.info('encfs hook: running fusermount -u %s' % mountpoint)
        os.spawnvp(os.P_WAIT, 'fusermount', ('fusermount', '-u', mountpoint))
	logging.info('encfs hook: removing intermediate mountpoint %s' % mountpoint)
        os.rmdir(mountpoint)
	logging.info('encfs hook: cleanup complete, exiting')
        sys.exit(exitCode)

      rc = os.spawnvp(os.P_WAIT, 'encfs', ('encfs', mountpoint, clearMountPoint))
      if not rc == 0:
	logging.error('encfs error (RC = %s), exiting...' % rc)
	cleanup(2)
      else:
        logging.info('encfs backgrounded')

      if os.fork() == 0:
        # this is the child that will periodically monitor encfs
        while os.path.ismount(clearMountPoint):
          logging.debug('waiting for %s to become unmounted' % clearMountPoint)
          time.sleep(ENCFS_MOUNTPOINT_CHECK_DELAY)
        logging.info('%s is not a mountpoint anymore, which means encfs has exited' % clearMountPoint)
        cleanup(0)
      else: # let the parent exit
        pass
    else: # encfs not requested, let the parent exit
      pass

if __name__ == '__main__':
  pyfile, mountpoint, namedOptions, useEncfs = parseCommandLineArgs(sys.argv[1:])

  # import picasafs from the pyfile location
  sys.path.append(os.path.abspath(os.path.dirname(pyfile)))
  import picasafs

  main(mountpoint, namedOptions, useEncfs)

